﻿Imports System.IO

Public Class LuxDCMTP

#Region "LuxDCMTP defines"
    Private Const MTPAXE_M_QUIT As Short = 0
    Private Const MTPAXE_M_WMDMAUTHENTICATE = 1
    Private Const MTPAXE_M_DEVICEMANAGER_GETREVISION = 10
    Private Const MTPAXE_M_DEVICEMANAGER_GETDEVICECOUNT = 11
    Private Const MTPAXE_M_DEVICEMANAGER_ENUMERATEDEVICES = 12
    Private Const MTPAXE_M_DEVICEMANAGER_SETCURRENTDEVICE = 13
    Private Const MTPAXE_M_DEVICE_GETMANUFACTURER = 20
    Private Const MTPAXE_M_DEVICE_GETTYPE = 21
    Private Const MTPAXE_M_DEVICE_ENUMERATESTORAGE = 22
    Private Const MTPAXE_M_DEVICE_GETSUPPORTEDFORMATS = 23
    Private Const MTPAXE_M_DEVICE_GETADDITIONALINFO = 24
    Private Const MTPAXE_M_DEVICE_GETICON = 25
    Private Const MTPAXE_M_DEVICE_CREATEPLAYLIST = 26
    Private Const MTPAXE_M_DEVICE_CREATEALBUM = 27
    Private Const MTPAXE_M_PLAYLIST_ENUMERATECONTENTS = 30
    Private Const MTPAXE_M_STORAGE_GETSIZEINFO = 40
    Private Const MTPAXE_M_STORAGE_CREATEFROMFILE = 41
    Private Const MTPAXE_M_STORAGE_DELETE = 42
    Private Const MTPAXE_M_STORAGE_GETALBUMARTIMAGE = 43

    Public Const WMDM_FILE_ATTR_FOLDER = &H8
    Public Const WMDM_FILE_ATTR_FILE = &H20
#End Region

    Private WithEvents LuxDCMTP As Process
    Private sIn As StreamReader
    Private sOut As StreamWriter
    Private sErr As StreamReader
    Private currentDevice As String

    Public Function startLuxDCMTP() As Boolean
        LuxDCMTP = New Process
        LuxDCMTP.StartInfo.FileName = System.IO.Path.Combine(Application.StartupPath, "MTPAxe.exe")
        LuxDCMTP.StartInfo.UseShellExecute = False
        LuxDCMTP.StartInfo.CreateNoWindow = False
        LuxDCMTP.StartInfo.RedirectStandardInput = True
        LuxDCMTP.StartInfo.RedirectStandardOutput = True
        LuxDCMTP.StartInfo.RedirectStandardError = True
        LuxDCMTP.EnableRaisingEvents = True

        Try
            LuxDCMTP.Start()

            sOut = LuxDCMTP.StandardInput
            sOut.AutoFlush = True
            sIn = LuxDCMTP.StandardOutput
            sErr = LuxDCMTP.StandardError

            sOut.WriteLine(MTPAXE_M_WMDMAUTHENTICATE)

            Dim s As String
            s = sIn.ReadLine

            If Not s = "0" Then
                Trace.WriteLine("LuxDCMTP error - " & sErr.ReadLine)
                Return False
            End If

        Catch ex As Exception
            Trace.WriteLine("Error initializing LuxDCMTP: " & ex.Message)
            LuxDCMTP = Nothing
            Return False
        End Try

        Return True
    End Function

    Public Function stopLuxDCMTP() As Boolean
        If LuxDCMTP Is Nothing Then
            Return "-1"
            Exit Function
        End If

        sOut.WriteLine(MTPAXE_M_QUIT)
        LuxDCMTP.WaitForExit(4000)

        If LuxDCMTP IsNot Nothing Then LuxDCMTP.Kill()

        sOut.Close()
        sIn.Close()
        sErr.Close()

        LuxDCMTP = Nothing
        Return True
    End Function

#Region "Device Manager Functions"

    Public Function getDeviceManagerRevision() As String
        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return ""
        End If


        sOut.WriteLine(MTPAXE_M_DEVICEMANAGER_GETREVISION)
        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return ""
        End If

        Return s
    End Function

    Public Function getDeviceCount() As String
        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICEMANAGER_GETDEVICECOUNT)
        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function enumerateDevices() As String
        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICEMANAGER_ENUMERATEDEVICES)
        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function setCurrentDevice(ByVal devName As String) As String
        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDC MTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICEMANAGER_SETCURRENTDEVICE)
        sOut.WriteLine(devName)

        Dim s As String = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return "-1"
        End If

        Me.currentDevice = devName
        Return "0"
    End Function

#End Region

#Region "Device functions"

    Private Function enumerateStorage() As String
        Trace.WriteLine("LuxDCMTP: enumerating storage")

        If stopLuxDCMTP() Then
            If Not startLuxDCMTP() Then
                LuxDCMTP = Nothing
            Else
                If Me.enumerateDevices() <> "" Then
                    Me.setCurrentDevice(currentDevice)
                End If
            End If
        End If

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICE_ENUMERATESTORAGE)

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: enumerating storage error - " & sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function getDeviceManufacturer() As String
        Trace.WriteLine("LuxDCMTP: getting device manufacturer")

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICE_GETMANUFACTURER)

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: getting device manufacturer error - " & sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function getDeviceType() As String
        Trace.WriteLine("LuxDCMTP: getting device type")

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_DEVICE_GETTYPE)

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: getting device type error - " & sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function getDeviceAdditionalInfo() As String
        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return ""
        End If

        sOut.WriteLine(MTPAXE_M_DEVICE_GETADDITIONALINFO)
        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return ""
        End If

        Return s
    End Function

    Public Function getDeviceSupportedFormats() As String
        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return ""
        End If

        sOut.WriteLine(MTPAXE_M_DEVICE_GETSUPPORTEDFORMATS)
        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine(sErr.ReadLine)
            Return ""
        End If

        Return s
    End Function

    Public Function getFullTreeView() As TreeView
        Trace.WriteLine("LuxDCMTP: building full directory tree")

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return New TreeView
        End If

        Dim theTreeView As TreeView = New TreeView

        Dim strTree As String
        strTree = enumerateStorage()
        If Not strTree = "-1" Then
            Try
                theTreeView = buildTreeViewFromDirectoryTreePtr(Integer.Parse(strTree.Split(":")(0), Globalization.NumberStyles.HexNumber), Integer.Parse(strTree.Split(":")(1)), False)

                With theTreeView
                    .Size = New Size(511, 290)
                End With

            Catch ex As Exception
                theTreeView = New TreeView
                Trace.WriteLine("LuxDCMTP: building full directory tree - empty tree returned")
            End Try
        End If

        Return theTreeView
    End Function

    Public Function findTreeNodeByID(ByRef root As TreeNode, ByVal ID As String) As TreeNode
        Dim ret As TreeNode = Nothing
        Dim item As StorageItem

        item = CType(root.Tag, StorageItem)
        If item.ID = ID Then
            Return root
        End If

        For Each node As TreeNode In root.Nodes
            item = CType(node.Tag, StorageItem)
            If item.ID = ID Then
                ret = node
            ElseIf node.Nodes.Count > 0 Then
                ret = findTreeNodeByID(node, ID)
            End If

            If ret IsNot Nothing Then
                Return ret
            End If
        Next

        Return ret
    End Function

    Private Function buildTreeViewFromDirectoryTreePtr(ByVal arrStorageItemsPtr As Integer, ByVal numItems As Integer, ByVal usePlaylistMode As Boolean) As TreeView
        Dim treenodes() As TreeNode
        Dim tn As TreeNode
        Dim index As Integer = 0
        Dim treeview1 As New TreeView

        Dim shinfo As New SHFILEINFO()
        Dim hImgSmall As IntPtr
        Dim myIcon As Icon
        Dim fileExt As String
        Dim imglst As New ImageList
        imglst.ColorDepth = ColorDepth.Depth32Bit
        treeview1.ImageList = imglst
        shinfo.szDisplayName = New String(Chr(0), 260)
        shinfo.szTypeName = New String(Chr(0), 80)
        Dim item As StorageItem

        Dim memreader As New ProcessMemoryReaderWriterLib.ProcessMemoryReaderWriter
        Dim buffer() As Byte
        Dim bytesRead As Integer
        memreader.ReadWriteProcess = LuxDCMTP
        memreader.OpenProcess()

        Dim minLevel As Short = -1, maxLevel As Short = -1

        ReDim treenodes(numItems - 1)

        Dim sizeofStruct As Integer = 64
        For i As Integer = 0 To numItems - 1

            item = New StorageItem

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 8, 4, bytesRead)
            item.DirectoryDepth = BitConverter.ToInt32(buffer, 0)

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 12, 4, bytesRead)
            item.StorageType = BitConverter.ToInt32(buffer, 0)

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 16, 8, bytesRead)
            item.Size = BitConverter.ToInt64(buffer, 0)

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 24, 4, bytesRead)
            item.ParentFileName = readWideCharFromPointer(memreader, BitConverter.ToInt32(buffer, 0))

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 28, 4, bytesRead)
            item.FileName = readWideCharFromPointer(memreader, BitConverter.ToInt32(buffer, 0))

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 56, 4, bytesRead)
            item.ID = readWideCharFromPointer(memreader, BitConverter.ToInt32(buffer, 0))

            buffer = memreader.ReadProcessMemory(arrStorageItemsPtr + (i * sizeofStruct) + 60, 4, bytesRead)
            item.ParentID = readWideCharFromPointer(memreader, BitConverter.ToInt32(buffer, 0))

            If minLevel = -1 And maxLevel = -1 Then
                minLevel = item.DirectoryDepth
                maxLevel = item.DirectoryDepth
            End If
            If item.DirectoryDepth < minLevel Then
                minLevel = item.DirectoryDepth
            End If
            If item.DirectoryDepth > maxLevel Then
                maxLevel = item.DirectoryDepth
            End If

            tn = New TreeNode
            tn.Tag = item
            tn.Text = item.FileName

            fileExt = IO.Path.GetExtension(tn.Text)
            tn.ImageKey = fileExt
            tn.SelectedImageKey = fileExt

            If (item.StorageType And WMDM_FILE_ATTR_FILE) = WMDM_FILE_ATTR_FILE Then

                If Not imglst.Images.ContainsKey(fileExt) Then
                    hImgSmall = SHGetFileInfo("x" & fileExt, _
                                                  FILE_ATTRIBUTE_NORMAL, _
                                                  shinfo, _
                                                  System.Runtime.InteropServices.Marshal.SizeOf(shinfo), _
                                                  SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_USEFILEATTRIBUTES)
                    myIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon)
                    imglst.Images.Add(tn.ImageKey, myIcon)
                    shinfo = Nothing
                End If

            Else

                If Not imglst.Images.ContainsKey("*") Then
                    hImgSmall = SHGetFileInfo("*", _
                                                  FILE_ATTRIBUTE_DIRECTORY, _
                                                  shinfo, _
                                                  System.Runtime.InteropServices.Marshal.SizeOf(shinfo), _
                                                  SHGFI_ICON Or SHGFI_SMALLICON Or SHGFI_USEFILEATTRIBUTES)
                    myIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon)
                    imglst.Images.Add("*", myIcon)
                    shinfo = Nothing
                End If
                tn.ImageKey = "*"
                tn.SelectedImageKey = "*"
            End If

            treenodes(index) = tn
            index += 1
        Next
        memreader.CloseHandle()

        Dim nodeLevel As Integer, nodeType As Integer, nodeParent As String
        If Not usePlaylistMode Then
            For index = minLevel To maxLevel

                For Each tn In treenodes

                    item = CType(tn.Tag, StorageItem)
                    nodeLevel = item.DirectoryDepth
                    nodeType = item.StorageType
                    nodeParent = item.ParentFileName

                    If item.DirectoryDepth = index Then
                        If index = minLevel Then
                            treeview1.Nodes.Add(tn)
                        Else
                            Dim parent As TreeNode = Nothing
                            For Each tnode As TreeNode In treeview1.Nodes
                                parent = findTreeNodeByID(tnode, item.ParentID)
                                If parent Is Nothing Then
                                    Trace.WriteLine("error building treeview")
                                    Throw New Exception("Error building treeview")
                                End If
                            Next
                            parent.Nodes.Add(tn)

                        End If
                    End If

                Next
            Next
        Else
            For Each tn In treenodes
                treeview1.Nodes.Add(tn)
            Next
        End If

        Return treeview1
    End Function

    Public Function uploadFile(ByVal path As String, ByVal destinationID As String, ByVal type As Integer, Optional ByVal metadata As StorageItem = Nothing) As String
        'destination=persistentuniqueid
        'type 0=file 1=folder

        If type = 0 Then
            Trace.WriteLine("LuxDCMTP: uploading file " & path)
        Else
            Trace.WriteLine("LuxDCMTP: creating folder " & path)
        End If

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        If metadata Is Nothing Then metadata = New StorageItem

        path = path.Trim
        If path.EndsWith(".") Then
            path = path.Substring(0, path.Length - 2)
        End If
        If path.StartsWith(".") Then
            path = path.Substring(1, path.Length - 1)
        End If

        sOut.WriteLine(MTPAXE_M_STORAGE_CREATEFROMFILE)

        sOut.WriteLine(writeWideCharToPointer(path.Replace("\"c, "\\")))
        sOut.WriteLine(writeWideCharToPointer(destinationID))
        sOut.WriteLine(writeWideCharToPointer(type))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.Title = "", "`", metadata.Title)))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.AlbumArtist = "", "`", metadata.AlbumArtist)))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.AlbumTitle = "", "`", metadata.AlbumTitle)))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.Genre = "", "`", metadata.Genre)))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.Year = "", "`", metadata.Year)))
        sOut.WriteLine(writeWideCharToPointer(IIf(metadata.TrackNum = "", "`", metadata.TrackNum)))

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: upload file - " & sErr.ReadLine)
            Return "-1"
        Else
            Trace.WriteLine("LuxDCMTP: upload succeeded!")
        End If

        Return s
    End Function

    Public Function deleteFile(ByVal storageID As String)
        'deletes the storage specified by StorageID from the device
        'returns 0 on ok, -1 on error

        Trace.WriteLine("LuxDCMTP: deleting - " & storageID)

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_STORAGE_DELETE)
        sOut.WriteLine(storageID)

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: error deleting " & storageID & " - " & sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

    Public Function getDeviceCapacity() As String
        Trace.WriteLine("LuxDCMTP: getting device capacity")

        Dim s As String

        If LuxDCMTP Is Nothing Then
            Trace.WriteLine("LuxDCMTP is not started")
            Return "-1"
        End If

        sOut.WriteLine(MTPAXE_M_STORAGE_GETSIZEINFO)

        s = sIn.ReadLine

        If s = "-1" Then
            Trace.WriteLine("LuxDCMTP: getting device capacity error - " & sErr.ReadLine)
            Return "-1"
        End If

        Return s
    End Function

#End Region

    Private Sub LuxDCMTP_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles LuxDCMTP.Exited
        If LuxDCMTP.ExitCode <> 0 Then
            LuxDCMTP = Nothing
            MsgBox("LuxDCMTP exited unexpectedly.", MsgBoxStyle.Critical Or MsgBoxStyle.SystemModal)
            Trace.WriteLine("LuxDCMTP exited unexpectedly")
        Else
            LuxDCMTP = Nothing
            Trace.WriteLine("LuxDCMTP exited successfully")
        End If
    End Sub

    Private Function readWideCharFromPointer(ByVal memreader As ProcessMemoryReaderWriterLib.ProcessMemoryReaderWriter, ByVal ptrToWcharStr As IntPtr) As String
        Dim buffer() As Byte
        Dim bytesRead As Integer
        Dim retstr As String = ""

        Do
            buffer = memreader.ReadProcessMemory(ptrToWcharStr, 2, bytesRead)
            If bytesRead <> 2 Then
                buffer(0) = 0
                buffer(1) = 0
            Else
                If Not (buffer(0) = 0 And buffer(1) = 0) Then
                    retstr = retstr & BitConverter.ToChar(buffer, 0)
                    ptrToWcharStr = New IntPtr(ptrToWcharStr.ToInt32 + 2)
                End If
            End If
        Loop Until (buffer(0) = 0 And buffer(1) = 0)

        Return retstr
    End Function

    Private Function writeWideCharToPointer(ByVal str As String) As String
        Dim enc As New System.Text.UnicodeEncoding
        Dim bytearr() = enc.GetBytes(str)

        Dim memwriter As New ProcessMemoryReaderWriterLib.ProcessMemoryReaderWriter
        memwriter.ReadWriteProcess = LuxDCMTP
        memwriter.OpenProcess()
        Dim loc As IntPtr = memwriter.AllocateAndWriteData(bytearr)

        Dim ret As String
        If loc.ToInt32 = 0 Then
            Trace.WriteLine("allocate and write failed")
            ret = "0"
        Else
            ret = loc.ToString
        End If

        memwriter.CloseHandle()
        Return ret
    End Function

End Class